use glam::{dvec2, dvec3, DVec2, DVec3};

use crate::{Hitables, HitRecord, Hitable, Interval, MaterialRef, Ray, AABB};

#[allow(non_snake_case)]
pub struct Quad {
    pub Q: DVec3,
    pub u: DVec3,
    pub v: DVec3,
    pub w: DVec3,
    pub material: MaterialRef,
    pub aabb: AABB,
    pub normal: DVec3,
    pub D: f64,
}

impl Quad {
    #[allow(non_snake_case)]
    pub fn new(Q: DVec3, u: DVec3, v: DVec3, material: MaterialRef) -> Quad {
        let aabb1 = AABB::from_two_point(Q, Q + u + v);
        let aabb2 = AABB::from_two_point(Q + u, Q + v);

        let n = u.cross(v);
        let normal = n.normalize();
        let D = normal.dot(Q);
        let w = n / n.dot(n);

        Self {
            Q,
            u,
            v,
            w,
            material,
            aabb: AABB::from_two_aabb(&aabb1, &aabb2),
            normal,
            D,
        }
    }

    fn is_interior(&self, uv: DVec2) -> bool {
        let unit_interval = Interval::new(0.0, 1.0);

        unit_interval.contains(uv.x) && unit_interval.contains(uv.y)
    }
}

impl Hitable for Quad {
    fn hit(&self, ray: &Ray, interval: Interval) -> Option<HitRecord> {
        let denom = self.normal.dot(ray.dir);

        // not hit, ray is parallel to the plane
        if denom.abs() < 1e-8 { return None }

        let t = (self.D - self.normal.dot(ray.orig)) / denom;
        if !interval.contains(t) { return None }

        let p = ray.at(t);
        let planar_hitpt_vector = p - self.Q;
        let uv = dvec2(
            self.w.dot(planar_hitpt_vector.cross(self.v)),
            self.w.dot(self.u.cross(planar_hitpt_vector)),
        );

        if !self.is_interior(uv) { return None }

        let mut hit = HitRecord::new(t, p, self.material.clone());
        hit.uv = uv;
        hit.set_face_normal(&ray.dir, &self.normal);

        Some(hit)
    }

    fn bounding_box(&self) -> &AABB {
        &self.aabb
    }
}

/// create a box
#[inline]
pub fn make_box(a: DVec3, b: DVec3, mat: MaterialRef) -> Box<impl Hitable>
{
    let mut box_quads = Hitables::default();
 
    let min = a.min(b);
    let max = a.max(b);

    let dx = dvec3(max.x - min.x, 0.0, 0.0);
    let dy = dvec3(0.0, max.y - min.y, 0.0);
    let dz = dvec3(0.0, 0.0, max.z - min.z);

    box_quads.add(Quad::new(dvec3(min.x, min.y, max.z),  dx,  dy, mat.clone()));
    box_quads.add(Quad::new(dvec3(max.x, min.y, max.z), -dz,  dy, mat.clone()));
    box_quads.add(Quad::new(dvec3(max.x, min.y, min.z), -dx,  dy, mat.clone()));
    box_quads.add(Quad::new(dvec3(min.x, min.y, min.z),  dz,  dy, mat.clone()));
    box_quads.add(Quad::new(dvec3(min.x, max.y, max.z),  dx, -dz, mat.clone()));
    box_quads.add(Quad::new(dvec3(min.x, min.y, min.z),  dx,  dz, mat.clone()));

    Box::new(box_quads)
}